/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2020  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"
#include "Global.h"
#include "common/Assertions.h"

static const s32 ADSR_MAX_VOL = 0x7fffffff;

static const int InvExpOffsets[] = {0, 4, 6, 8, 9, 10, 11, 12};
static u32 PsxRates[160];


void InitADSR() // INIT ADSR
{
	for (int i = 0; i < (32 + 128); i++)
	{
		int shift = (i - 32) >> 2;
		s64 rate = (i & 3) + 4;
		if (shift < 0)
			rate >>= -shift;
		else
			rate <<= shift;

		// Maximum rate is 0x4000.
		PsxRates[i] = (int)std::min(rate, (s64)0x40000000LL);
	}
}

bool V_ADSR::Calculate()
{
	pxAssume(Phase != 0);

	if (Releasing && (Phase < 5))
		Phase = 5;

	switch (Phase)
	{
		case 1: // attack
			if (Value == ADSR_MAX_VOL)
			{
				// Already maxed out.  Progress phase and nothing more:
				Phase++;
				break;
			}

			// Case 1 below is for pseudo exponential below 75%.
			// Pseudo Exp > 75% and Linear are the same.

			if (AttackMode && (Value >= 0x60000000))
				Value += PsxRates[(AttackRate ^ 0x7f) - 0x18 + 32];
			else
				Value += PsxRates[(AttackRate ^ 0x7f) - 0x10 + 32];

			if (Value < 0)
			{
				// We hit the ceiling.
				Phase++;
				Value = ADSR_MAX_VOL;
			}
			break;

		case 2: // decay
		{
			u32 off = InvExpOffsets[(Value >> 28) & 7];
			Value -= PsxRates[((DecayRate ^ 0x1f) * 4) - 0x18 + off + 32];

			// calculate sustain level as a factor of the ADSR maximum volume.

			s32 suslev = ((0x80000000 / 0x10) * (SustainLevel + 1)) - 1;

			if (Value <= suslev)
			{
				if (Value < 0)
					Value = 0;
				Phase++;
			}
		}
		break;

		case 3: // sustain
		{
			// 0x7f disables sustain (infinite sustain)
			if (SustainRate == 0x7f)
				return true;

			if (SustainMode & 2) // decreasing
			{
				if (SustainMode & 4) // exponential
				{
					u32 off = InvExpOffsets[(Value >> 28) & 7];
					Value -= PsxRates[(SustainRate ^ 0x7f) - 0x1b + off + 32];
				}
				else // linear
					Value -= PsxRates[(SustainRate ^ 0x7f) - 0xf + 32];

				if (Value <= 0)
				{
					Value = 0;
					Phase++;
				}
			}
			else
			{ // increasing
				if ((SustainMode & 4) && (Value >= 0x60000000))
					Value += PsxRates[(SustainRate ^ 0x7f) - 0x18 + 32];
				else
					// linear / Pseudo below 75% (they're the same)
					Value += PsxRates[(SustainRate ^ 0x7f) - 0x10 + 32];

				if (Value < 0)
				{
					Value = ADSR_MAX_VOL;
					Phase++;
				}
			}
		}
		break;

		case 4: // sustain end
			Value = (SustainMode & 2) ? 0 : ADSR_MAX_VOL;
			if (Value == 0)
				Phase = 6;
			break;

		case 5:              // release
			if (ReleaseMode) // exponential
			{
				u32 off = InvExpOffsets[(Value >> 28) & 7];
				Value -= PsxRates[((ReleaseRate ^ 0x1f) * 4) - 0x18 + off + 32];
			}
			else
			{ // linear
				//Value-=PsxRates[((ReleaseRate^0x1f)*4)-0xc+32];
				if (ReleaseRate != 0x1f)
					Value -= (1 << (0x1f - ReleaseRate));
			}

			if (Value <= 0)
			{
				Value = 0;
				Phase++;
			}
			break;

		case 6: // release end
			Value = 0;
			break;

			jNO_DEFAULT
	}

	// returns true if the voice is active, or false if it's stopping.
	return Phase != 6;
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
//                                                                                     //

#define VOLFLAG_REVERSE_PHASE (1ul << 0)
#define VOLFLAG_DECREMENT (1ul << 1)
#define VOLFLAG_EXPONENTIAL (1ul << 2)
#define VOLFLAG_SLIDE_ENABLE (1ul << 3)

void V_VolumeSlide::Update()
{
	if (!(Mode & VOLFLAG_SLIDE_ENABLE))
		return;

	// Volume slides use the same basic logic as ADSR, but simplified (single-stage
	// instead of multi-stage)

	if (Increment == 0x7f)
		return;

	s32 value = abs(Value);

	if (Mode & VOLFLAG_DECREMENT)
	{
		// Decrement

		if (Mode & VOLFLAG_EXPONENTIAL)
		{
			u32 off = InvExpOffsets[(value >> 28) & 7];
			value -= PsxRates[(Increment ^ 0x7f) - 0x1b + off + 32];
		}
		else
			value -= PsxRates[(Increment ^ 0x7f) - 0xf + 32];

		if (value < 0)
		{
			value = 0;
			Mode = 0; // disable slide
		}
	}
	else
	{
		// Increment
		// Pseudo-exponential increments, as done by the SPU2 (really!)
		// Above 75% slides slow, below 75% slides fast.  It's exponential, pseudo'ly speaking.

		if ((Mode & VOLFLAG_EXPONENTIAL) && (value >= 0x60000000))
			value += PsxRates[(Increment ^ 0x7f) - 0x18 + 32];
		else
			// linear / Pseudo below 75% (they're the same)
			value += PsxRates[(Increment ^ 0x7f) - 0x10 + 32];

		if (value < 0) // wrapped around the "top"?
		{
			value = 0x7fffffff;
			Mode = 0; // disable slide
		}
	}

	Value = (Value < 0) ? -value : value;
}
